using System.Collections.Generic;

namespace Lucene.Net.Index
{
    using Lucene.Net.Support;

    /*
         * Licensed to the Apache Software Foundation (ASF) under one or more
         * contributor license agreements.  See the NOTICE file distributed with
         * this work for additional information regarding copyright ownership.
         * The ASF licenses this file to You under the Apache License, Version 2.0
         * (the "License"); you may not use this file except in compliance with
         * the License.  You may obtain a copy of the License at
         *
         *     http://www.apache.org/licenses/LICENSE-2.0
         *
         * Unless required by applicable law or agreed to in writing, software
         * distributed under the License is distributed on an "AS IS" BASIS,
         * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
         * See the License for the specific language governing permissions and
         * limitations under the License.
         */

    using Directory = Lucene.Net.Store.Directory;

    /// <summary>
    /// Embeds a [read-only] SegmentInfo and adds per-commit
    ///  fields.
    ///
    ///  @lucene.experimental
    /// </summary>
    public class SegmentCommitInfo
    {
        /// <summary>
        /// The <seealso cref="SegmentInfo"/> that we wrap. </summary>
        public readonly SegmentInfo Info;

        // How many deleted docs in the segment:
        private int DelCount_Renamed;

        // Generation number of the live docs file (-1 if there
        // are no deletes yet):
        private long DelGen_Renamed;

        // Normally 1+delGen, unless an exception was hit on last
        // attempt to write:
        private long NextWriteDelGen;

        // Generation number of the FieldInfos (-1 if there are no updates)
        private long FieldInfosGen_Renamed;

        // Normally 1 + fieldInfosGen, unless an exception was hit on last attempt to
        // write
        private long NextWriteFieldInfosGen;

        // Track the per-generation updates files
        private readonly IDictionary<long, ISet<string>> GenUpdatesFiles_Renamed = new Dictionary<long, ISet<string>>();

        private long SizeInBytes_Renamed = -1;

        /// <summary>
        /// Sole constructor.
        /// </summary>
        /// <param name="info">
        ///          <seealso cref="SegmentInfo"/> that we wrap </param>
        /// <param name="delCount">
        ///          number of deleted documents in this segment </param>
        /// <param name="delGen">
        ///          deletion generation number (used to name deletion files) </param>
        /// <param name="fieldInfosGen">
        ///          FieldInfos generation number (used to name field-infos files)
        ///  </param>
        public SegmentCommitInfo(SegmentInfo info, int delCount, long delGen, long fieldInfosGen)
        {
            this.Info = info;
            this.DelCount_Renamed = delCount;
            this.DelGen_Renamed = delGen;
            if (delGen == -1)
            {
                NextWriteDelGen = 1;
            }
            else
            {
                NextWriteDelGen = delGen + 1;
            }

            this.FieldInfosGen_Renamed = fieldInfosGen;
            if (fieldInfosGen == -1)
            {
                NextWriteFieldInfosGen = 1;
            }
            else
            {
                NextWriteFieldInfosGen = fieldInfosGen + 1;
            }
        }

        /// <summary>
        /// Returns the per generation updates files. </summary>
        public virtual IDictionary<long, ISet<string>> UpdatesFiles
        {
            get
            {
                return CollectionsHelper.UnmodifiableMap(GenUpdatesFiles_Renamed);
            }
        }

        /// <summary>
        /// Sets the updates file names per generation. Does not deep clone the map. </summary>
        public virtual IDictionary<long, ISet<string>> GenUpdatesFiles
        {
            set
            {
                this.GenUpdatesFiles_Renamed.Clear();
                this.GenUpdatesFiles_Renamed.PutAll(value);
            }
        }

        /// <summary>
        /// Called when we succeed in writing deletes </summary>
        internal virtual void AdvanceDelGen()
        {
            DelGen_Renamed = NextWriteDelGen;
            NextWriteDelGen = DelGen_Renamed + 1;
            SizeInBytes_Renamed = -1;
        }

        /// <summary>
        /// Called if there was an exception while writing
        ///  deletes, so that we don't try to write to the same
        ///  file more than once.
        /// </summary>
        internal virtual void AdvanceNextWriteDelGen()
        {
            NextWriteDelGen++;
        }

        /// <summary>
        /// Called when we succeed in writing a new FieldInfos generation. </summary>
        internal virtual void AdvanceFieldInfosGen()
        {
            FieldInfosGen_Renamed = NextWriteFieldInfosGen;
            NextWriteFieldInfosGen = FieldInfosGen_Renamed + 1;
            SizeInBytes_Renamed = -1;
        }

        /// <summary>
        /// Called if there was an exception while writing a new generation of
        /// FieldInfos, so that we don't try to write to the same file more than once.
        /// </summary>
        internal virtual void AdvanceNextWriteFieldInfosGen()
        {
            NextWriteFieldInfosGen++;
        }

        /// <summary>
        /// Returns total size in bytes of all files for this
        ///  segment.
        /// <p><b>NOTE:</b> this value is not correct for 3.0 segments
        /// that have shared docstores. To get the correct value, upgrade!
        /// </summary>
        public virtual long SizeInBytes()
        {
            if (SizeInBytes_Renamed == -1)
            {
                long sum = 0;
                foreach (string fileName in Files())
                {
                    sum += Info.Dir.FileLength(fileName);
                }
                SizeInBytes_Renamed = sum;
            }

            return SizeInBytes_Renamed;
        }

        /// <summary>
        /// Returns all files in use by this segment. </summary>
        public virtual ICollection<string> Files()
        {
            // Start from the wrapped info's files:
            ISet<string> files = new HashSet<string>(Info.Files);

            // TODO we could rely on TrackingDir.getCreatedFiles() (like we do for
            // updates) and then maybe even be able to remove LiveDocsFormat.files().

            // Must separately add any live docs files:
            Info.Codec.LiveDocsFormat().Files(this, files);

            // Must separately add any field updates files
            foreach (ISet<string> updateFiles in GenUpdatesFiles_Renamed.Values)
            {
                CollectionsHelper.AddAll(files, updateFiles);
            }

            return files;
        }

        // NOTE: only used in-RAM by IW to track buffered deletes;
        // this is never written to/read from the Directory
        private long BufferedDeletesGen_Renamed;

        internal virtual long BufferedDeletesGen
        {
            get
            {
                return BufferedDeletesGen_Renamed;
            }
            set
            {
                BufferedDeletesGen_Renamed = value;
                SizeInBytes_Renamed = -1;
            }
        }

        /// <summary>
        /// Returns true if there are any deletions for the
        /// segment at this commit.
        /// </summary>
        public virtual bool HasDeletions()
        {
            return DelGen_Renamed != -1;
        }

        /// <summary>
        /// Returns true if there are any field updates for the segment in this commit. </summary>
        public virtual bool HasFieldUpdates()
        {
            return FieldInfosGen_Renamed != -1;
        }

        /// <summary>
        /// Returns the next available generation number of the FieldInfos files. </summary>
        public virtual long NextFieldInfosGen
        {
            get
            {
                return NextWriteFieldInfosGen;
            }
        }

        /// <summary>
        /// Returns the generation number of the field infos file or -1 if there are no
        /// field updates yet.
        /// </summary>
        public virtual long FieldInfosGen
        {
            get
            {
                return FieldInfosGen_Renamed;
            }
        }

        /// <summary>
        /// Returns the next available generation number
        /// of the live docs file.
        /// </summary>
        public virtual long NextDelGen
        {
            get
            {
                return NextWriteDelGen;
            }
        }

        /// <summary>
        /// Returns generation number of the live docs file
        /// or -1 if there are no deletes yet.
        /// </summary>
        public virtual long DelGen
        {
            get
            {
                return DelGen_Renamed;
            }
        }

        /// <summary>
        /// Returns the number of deleted docs in the segment.
        /// </summary>
        public virtual int DelCount
        {
            get
            {
                return DelCount_Renamed;
            }
            set
            {
                if (value < 0 || value > Info.DocCount)
                {
                    throw new System.ArgumentException("invalid delCount=" + value + " (docCount=" + Info.DocCount + ")");
                }
                this.DelCount_Renamed = value;
            }
        }

        /// <summary>
        /// Returns a description of this segment. </summary>
        public virtual string ToString(Directory dir, int pendingDelCount)
        {
            string s = Info.ToString(dir, DelCount_Renamed + pendingDelCount);
            if (DelGen_Renamed != -1)
            {
                s += ":delGen=" + DelGen_Renamed;
            }
            if (FieldInfosGen_Renamed != -1)
            {
                s += ":fieldInfosGen=" + FieldInfosGen_Renamed;
            }
            return s;
        }

        public override string ToString()
        {
            return ToString(Info.Dir, 0);
        }

        public virtual object Clone()
        {
            SegmentCommitInfo other = new SegmentCommitInfo(Info, DelCount_Renamed, DelGen_Renamed, FieldInfosGen_Renamed);
            // Not clear that we need to carry over nextWriteDelGen
            // (i.e. do we ever clone after a failed write and
            // before the next successful write?), but just do it to
            // be safe:
            other.NextWriteDelGen = NextWriteDelGen;
            other.NextWriteFieldInfosGen = NextWriteFieldInfosGen;

            // deep clone
            foreach (KeyValuePair<long, ISet<string>> e in GenUpdatesFiles_Renamed)
            {
                other.GenUpdatesFiles_Renamed[e.Key] = new HashSet<string>(e.Value);
            }

            return other;
        }
    }
}